﻿using System;
using System.Collections.Generic;
using System.Text;
using PW.Mini.SlepowronskiJ.Siec.Budowa;

namespace PW.Mini.SlepowronskiJ.Siec.Algorytmy
{
    /// <summary>
    /// metoda uczenia sieci elemana
    /// RPROP
    /// </summary>
    [Serializable]
    public class ElmanRPROPAlgorytmUczenia : RPROPAbstractAlgorytmUczenia
    {

        #region konstruktor

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu wstecznej propagacji bledu 
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        public ElmanRPROPAlgorytmUczenia()
            : base()
        {
            this.nazwa = "Elmana - RPROP";
        }

        /// <summary>
        /// tworzy nowy obiekt klasy algorytmu wstecznej propagacji bledu dla
        /// sieci neuronowej
        /// z parametrami wspolczynnikUczenia i momentu pobieranymi z Monitora
        /// </summary>
        /// <param name="siecNeuronowa">siec neuronowa do uczenia</param>
        public ElmanRPROPAlgorytmUczenia(SiecNeuronowa siecNeuronowa)
            : base(siecNeuronowa)
        {
            this.nazwa = "Elmana - RPROP";
        }

        public override AlgorytmUczenia UtworzNowyAlgorytm()
        {
            return new ElmanRPROPAlgorytmUczenia();
        }

        #endregion

        /// <summary>
        /// Metoda uczenia siec neuronowej z Walidacją na danych wejsciowych
        /// reprezentowanych przez macierz wartosci wraz z 
        /// oczekiwanymi wyjsciami dla tych danych 
        /// jesli wejscia lub wyjscia walidacyjne = null to bez walidacji
        /// </summary>
        /// <param name="wejscia">macierz wejsc testowych</param>
        /// <param name="wyjscia">macierz wyjsc oczekiwanych</param>
        /// <param name="wejsciaWalidacyjne">macierz wejsc walidacyjnych</param>
        /// <param name="wyjsciaWalidacyjne">macierz wyjsc walidacyjnych</param>
        public override void Uczenie(double[][] wejscia, double[][] wyjscia,
            double[][] wejsciaWalidacyjne, double[][] wyjsciaWalidacyjne)
        {
            base.Uczenie(wejscia, wyjscia, wejsciaWalidacyjne, wyjsciaWalidacyjne);
            double[] otrzymaneWyjscia;
            double bladTmp;
            double[][][][,] R = new double[wejscia.Length][][][,];
            double[][][,] newR = null;
            double[][][] A = new double[wejscia.Length][][];
            double[][][] ostatnieWejsciaNeuronow = new double[wejscia.Length][][];
            for (int nr = 0; nr < wejscia.Length; ++nr)
            {
                R[nr] = new double[siecNeuronowa.RozmiarSieci - 1][][,];
                if (nr == 0)
                {
                    newR = new double[siecNeuronowa.RozmiarSieci - 1][][,];
                    wspolczynnikUczenia = new double[siecNeuronowa.RozmiarSieci][][];
                    prevDelta = new double[siecNeuronowa.RozmiarSieci][][];
                }
                ostatnieWejsciaNeuronow[nr] = new double[siecNeuronowa.RozmiarSieci][];
                A[nr] = new double[siecNeuronowa.RozmiarSieci][];
                for (int l = 0; l < siecNeuronowa.RozmiarSieci; ++l)
                {
                    if (l != siecNeuronowa.RozmiarSieci - 1)
                    {
                        R[nr][l] = new double[siecNeuronowa[l].RozmiarWarstwy][,];
                        if (nr == 0)
                            newR[l] = new double[siecNeuronowa[l].RozmiarWarstwy][,];
                        for (int k = 0; k < siecNeuronowa[l].RozmiarWarstwy; ++k)
                        {
                            //dla warstw kontekstowych [liczbaNeuronowWWarstwie,liczba wejsc]
                            R[nr][l][k] = new double[siecNeuronowa[l].RozmiarWarstwy, siecNeuronowa[l][k].LiczbaWejsc];
                            if (nr == 0)
                                newR[l][k] = new double[siecNeuronowa[l].RozmiarWarstwy, siecNeuronowa[l][k].LiczbaWejsc];
                        }
                    }
                    if (nr == 0)
                    {
                        wspolczynnikUczenia[l] = new double[siecNeuronowa[l].RozmiarWarstwy][];
                        prevDelta[l] = new double[siecNeuronowa[l].RozmiarWarstwy][];
                        for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                        {
                            wspolczynnikUczenia[l][j] = new double[siecNeuronowa[l][j].LiczbaWejsc];
                            prevDelta[l][j] = new double[siecNeuronowa[l][j].LiczbaWejsc];
                            for (int k = 0; k < siecNeuronowa[l][j].LiczbaWejsc; ++k)
                                wspolczynnikUczenia[l][j][k] = monitor.WspolczynnikUczenia;
                        }
                    }
                    A[nr][l] = new double[siecNeuronowa[l].RozmiarWarstwy];
                    ostatnieWejsciaNeuronow[nr][l] = new double[siecNeuronowa[l].LiczbaWejsc + siecNeuronowa[l].LiczbaKontekstow];
                    //zapisuje wzorce wejsciowe jako wejscia do pierwszej warstwy (beda uzupelniane kontekstowe na biezaco)
                    if (l == 0)
                        for (int i = 0; i < wejscia[nr].Length; ++i)
                            ostatnieWejsciaNeuronow[nr][0][i] = wejscia[nr][i];

                }
            }
            bool wyjsciaBipolarne = true;
            for (int i = 0; i < wyjsciaWalidacyjne.Length; ++i)
                for (int j = 0; j < wyjsciaWalidacyjne[i].Length; ++j)
                    if (wyjsciaWalidacyjne[i][j] != 1 && wyjsciaWalidacyjne[i][j] != -1)
                    {
                        wyjsciaBipolarne = false;
                        break;
                    }
            WzrostBleduWalidacyjnegoStrategiaSN wbws;
            if (wyjsciaBipolarne)
                wbws = new WzrostBleduWalidacyjnegoKlasyfikatorStrategiaSN(3, false);
            else
                wbws = new WzrostBleduWalidacyjnegoStrategiaSN(3, false);
            wbws.DaneWalidacyjne = new double[][][] { wejsciaWalidacyjne, wyjsciaWalidacyjne };
            StopMalySpadekStrategia stop = new StopMalySpadekStrategia(
                Math.Min(StopMalySpadekStrategia.domyslnaMinimalnaPoprawa,
                this.monitor.WarunekStopBleduSieci), StopMalySpadekStrategia.domyslnaLiczbaIteracji);
            this.strategie.Clear();
            this.strategie.Add(wbws);
            this.strategie.Add(stop);
            this.Init();
            do
            {
                this.PreIteration();
                bladTmp = 0.0;
                //todo losowanie kolejnosci wzorcow do poprawek
                //this.monitor.LosujKolejnosc(ref kolejnoscWzorcow);
                for (int i = 0; i < wejscia.Length; ++i)
                {
                    otrzymaneWyjscia = siecNeuronowa.Uruchom(wejscia[kolejnoscWzorcow[i]]);
                    bladTmp += Monitor.MiaryBledow.SrednioKwadratowySkroconyRefWB(
                        ref wektorBledow, wyjscia[kolejnoscWzorcow[i]], otrzymaneWyjscia);
                    WyliczPoprawki(kolejnoscWzorcow[i], A, R, newR, ostatnieWejsciaNeuronow);
                }
                UstawWagi(A, R, ostatnieWejsciaNeuronow);
                this.monitor.BladSieci = bladTmp / wejscia.Length;
                this.PostIteration();
            }
            while (this.monitor.NumerIteracji < this.monitor.MaxLiczbaIteracji &&
                this.monitor.BladSieci > this.monitor.WarunekStopBleduSieci &&
                this.monitor.UczenieSieci && !stop.ShouldStop());
            StringBuilder strBld = PobierzKomunikat();
            if (wbws.ShouldStop())
            {
                //wzrostBleduWalidacyjnegoStrategia.PrzywrocNajlepszaSiec();
                strBld.AppendLine("uczenie przerwane z powodu trzykrotnego wzrostu błędu walidacyjnego");
            }
            if (stop.ShouldStop())
                strBld.AppendLine("uczenie przerwane z powodu zbyt małych postępów uczenia (błąd malał o mniej niż " +
                    Math.Min(StopMalySpadekStrategia.domyslnaMinimalnaPoprawa, this.monitor.WarunekStopBleduSieci) 
                    + " przez " + StopMalySpadekStrategia.domyslnaLiczbaIteracji + " iteracji)");
            this.komunikat = strBld.ToString();
            this.monitor.Logger.SendString(komunikat);
        }

        protected void WyliczPoprawki(int nr, double[][][] A, double[][][][,] R,
            double[][][,] newR, double[][][] ostatnieWejsciaNeuronow)
        {
            double sk;
            int l = siecNeuronowa.RozmiarSieci - 1;
            // dla ostatniej warstwy
            //Aj = f`(suma sygnalow wejsciowych do neuronu wyjsciowego j)*(j-ta wartosc oczekiwana - j-te wyjscie)
            for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                A[nr][l][j] = siecNeuronowa[l][j].A = siecNeuronowa[l][j].PochodnaWyjscia * wektorBledow[j];
            //zapisuje wejscia do ostatniej warstwy
            for (int m = 0; m < siecNeuronowa[l].LiczbaWejsc; ++m)
                ostatnieWejsciaNeuronow[nr][l][m] = siecNeuronowa[l - 1].OstatnieWyjscia[m];
            // dla pozostalych warstw, czyli warstw ukrytych
            for (--l; l >= 0; --l)
            {
                if (l > 0)
                    for (int j = 0; j < siecNeuronowa[l].LiczbaWejsc; ++j)
                        ostatnieWejsciaNeuronow[nr][l][j] = siecNeuronowa[l - 1].OstatnieWyjscia[j];
                //warstwa l-ta - ukryta
                //ustawic trzeba odpowiednio rozszerzoneWejscia do warstwy l-tej
                for (int j = siecNeuronowa[l].LiczbaWejsc; j < siecNeuronowa[l].LiczbaWejsc + siecNeuronowa[l].LiczbaKontekstow; ++j)
                    ostatnieWejsciaNeuronow[nr][l][j] = ((ElmanSiecNeuronowa)siecNeuronowa).Kontekst[l].OstatnieWyjscia[j - siecNeuronowa[l].LiczbaWejsc];
                for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                {
                    //Aj z warstwy l= suma(wagi pomiedzy j-tym ukrytym oraz wyjsciem k-tym * (Ak z warstwy l+1))
                    //neuron j-ty w warstwie ukrytej
                    sk = 0.0;
                    for (int k = 0; k < siecNeuronowa[l + 1].RozmiarWarstwy; ++k)
                        sk += siecNeuronowa[l + 1][k].A * siecNeuronowa[l + 1][k][j];
                    //pochodna sumy wejscia w j-tym neuron ukryty jest uzywana do obliczania R
                    A[nr][l][j] = siecNeuronowa[l][j].A = sk;

                    //obliczamy R - dla kazdego neuronu w warstwie ukrytej odpowiada Rkm
                    for (int k = 0; k < siecNeuronowa[l].RozmiarWarstwy; ++k)
                        for (int m = 0; m < siecNeuronowa[l][j].LiczbaWejsc; ++m)
                        {
                            sk = 0.0;
                            //waga z warstwy l-tej pomiedzy n-tym jej neuronem a j-tym kontekstowym
                            //inaczej mowiac jak wplywaly poprzednie wyjscia warstwy l na dany neuron j
                            for (int n = 0; n < siecNeuronowa[l].LiczbaKontekstow; ++n)
                                sk += siecNeuronowa[l][j][n + siecNeuronowa[l].LiczbaWejsc] * R[nr][l][n][k, m];
                            //nowe Rj[k,m] = pochodna sygnalu wejsciowego(j) *(sk + deltaKronecera * sygnal wejsciowy m)
                            double sygnalWejsciowyM_deltaKronec = (k == j ? ostatnieWejsciaNeuronow[nr][l][m] : 0);
                            //bierzemy z kontekstow
                            newR[l][j][k, m] = siecNeuronowa[l][j].PochodnaWyjscia * (sk + sygnalWejsciowyM_deltaKronec);
                        }
                }
                //po wyliczeniu wszystkich nowych R w chwili (n+1) moge je przepisac do tablicy R
                for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                    for (int k = 0; k < siecNeuronowa[l].RozmiarWarstwy; ++k)
                        for (int m = 0; m < siecNeuronowa[l][j].LiczbaWejsc; ++m)
                            //todo / wejscia.Length
                            R[nr][l][j][k, m] = newR[l][j][k, m] / wejscia.Length;
            }
        }

        /// <summary>
        /// ustawia poprawione wagi
        /// </summary>
        /// <param name="gradient">wyznaczony gradient do poprawy wagi</param>
        protected void UstawWagi(double[][][] A, double[][][][,] R, double[][][] ostatnieWejsciaNeuronow)
        {
            int l = 0;
            //wszystkie bez ostatniej warstwy
            for (; l < siecNeuronowa.RozmiarSieci - 1; ++l)
            {
                //j-ty neuron warstwy ukrytej
                for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
                {
                    //waga z m-tego neuronu wejscia(l-1) do j-tego neuronu warstwy ukrytej
                    for (int m = 0; m < siecNeuronowa[l][j].LiczbaWejsc; ++m)
                    {
                        double sk = 0.0;
                        //iteruje po wszystkich pokazanych wzorcach
                        for (int nr = 0; nr < wejscia.Length; ++nr)
                            for (int k = 0; k < siecNeuronowa[l].RozmiarWarstwy; ++k)
                                sk += A[nr][l][k] * R[nr][l][k][j, m];
                        siecNeuronowa[l][j][m] += wyliczWspolczynnikUczenia(l, j, m, -sk, siecNeuronowa[l][j][m] - siecNeuronowa[l][j].PoprzednieWagi[m])
                           + this.monitor.Moment * (siecNeuronowa[l][j][m] - siecNeuronowa[l][j].PoprzednieWagi[m]);
                    }
                }
            }
            //dla ostatniej warstwy zmiany
            for (int j = 0; j < siecNeuronowa[l].RozmiarWarstwy; ++j)
            {
                for (int m = 0; m < siecNeuronowa[l][j].LiczbaWejsc; ++m)
                {
                    double sk = 0.0d;
                    //iteruje po wszystkich pokazanych wzorcach
                    for (int nr = 0; nr < wejscia.Length; ++nr)
                        sk += A[nr][l][j] * ostatnieWejsciaNeuronow[nr][l][m];
                    siecNeuronowa[l][j][m] += wyliczWspolczynnikUczenia(l, j, m, -sk, siecNeuronowa[l][j][m] - siecNeuronowa[l][j].PoprzednieWagi[m])
                        + this.monitor.Moment * (siecNeuronowa[l][j][m] - siecNeuronowa[l][j].PoprzednieWagi[m]);
                }
            }
        }
    }
}
